﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace FileTranslator
{
    public abstract class ConstraintHint
    {
        // 以后需要参考的表，例如外键所对应的表
        public DataTable ReferenceTable { get; set; }

        protected string columnNameNeedsConstraint = null;
        public virtual string ColumnNameNeedsConstraint 
        {
            get
            {
                return columnNameNeedsConstraint;
            }
            set
            {
                columnNameNeedsConstraint = value;
            }
        }

        public ConstraintHint(string keyColumn, DataTable dt = null)
        {
            ColumnNameNeedsConstraint = keyColumn;
            ReferenceTable = dt;
        }

        // 检查约束是否符合
        public bool Verify(DataView table)
        {
            // 先检查其他约束
            foreach (ConstraintHint cc in OtherConstraints)
            {
                cc.Verify(table);
            }

            // 检查自身约束
            if (table == null)
            {
                // TODO: log
                throw new System.Exception("Data table has not been set");
            }

            int i = 0;
            foreach (DataRow dr in table.ToTable().Rows)
            {
                if (DoVerify(dr, i) == false)
                {
                    return false;
                }
                i++;
            }

            return true;
        }

        public bool Verify(DataRow dr, int position)
        {
            bool result = true;
            foreach (ConstraintHint cc in OtherConstraints)
            {
                result &= cc.DoVerify(dr, position);

                if (result == false)
                {
                    return result;
                }
            }
            result &= DoVerify(dr, position);

            return result;
        }

        // dr是目前所处理的DataRow, i表示当前DataRow在DataTable中的位置
        protected abstract bool DoVerify(DataRow dr, int i);

        // 返回true表示并没有违反约束（Fix没有修改），false表示验证失败并进行了修改
        public bool Fix(DataView table, int position)
        {
            // 修复其他约束
            foreach (ConstraintHint cc in OtherConstraints)
            {
                cc.Fix(table, position);
            }

            // 修复自身约束
            if (table == null)
            {
                // TODO: log
                throw new System.Exception("Data table has not been set");
            }

            bool result = true;
            for (int i = position; i < table.Count; i++)
            {
                result &= DoFix(table[i].Row, i);
            }

            return result;
        }

        // 返回true表示并没有违反约束（Fix没有修改），false表示验证失败并进行了修改
        public bool Fix(DataRow dr, int position)
        {
            bool result = true;
            foreach (ConstraintHint cc in OtherConstraints)
            {
                result &= cc.DoFix(dr, position);

                if (result == false)
                {
                    return result;
                }
            }
            result &= DoFix(dr, position);

            return result;
        }

        // dr是目前所处理的DataRow, i表示当前DataRow在DataTable中的位置
        protected abstract bool DoFix(DataRow dr, int i);

        // Composite
        private List<ConstraintHint> otherConstraints = new List<ConstraintHint>();
        public List<ConstraintHint> OtherConstraints 
        {
            get
            {
                return otherConstraints;
            }
        }


        // Readonly?
        private bool isReadOnly = false;
        public bool IsReadOnly
        {
            get
            {
                return isReadOnly;
            }
            protected set
            {
                isReadOnly = value;
            }
        }
    }

    // 自动增长约束
    // 默认在DataTable中的类型是string
    public class AutoIncreaseConstraint : ConstraintHint
    {
        private int baseNum;
        private int increaseStep;

        public AutoIncreaseConstraint(string columnName, int baseNum, int increaseStep)
            : base(columnName)
        {
            this.baseNum = baseNum;
            this.increaseStep = increaseStep;
            IsReadOnly = true;
        }

        protected override bool DoVerify(DataRow dr, int i)
        {
            return dr[ColumnNameNeedsConstraint].ToString() == (baseNum + i * increaseStep).ToString();
        }

        protected override bool DoFix(DataRow dr, int i)
        {
            if (DoVerify(dr, i) == true)
            {
                return true;
            }
            dr[ColumnNameNeedsConstraint] = (baseNum + i * increaseStep).ToString();
            return false;
        }
    }

    // 同一列相同值约束
    // 默认在DataTable中的类型是string
    public class AllCellSameValueConstraint : ConstraintHint
    {
        public string Value { get; set; }
        public AllCellSameValueConstraint(string columnName, string constantValue = "")
            : base(columnName)
        {
            Value = constantValue;
        }

        protected override bool DoVerify(DataRow dr, int i)
        {
            return dr[ColumnNameNeedsConstraint].ToString() == Value.ToString();
        }

        protected override bool DoFix(DataRow dr, int i)
        {
            if (DoVerify(dr, i) == true)
            {
                return true;
            }
            dr[ColumnNameNeedsConstraint] = Value;
            return false;
        }
    }

    public class ReadonlyConstraint : ConstraintHint
    {
        public ReadonlyConstraint(string columnName)
            : base(columnName)
        {
            IsReadOnly = true;
        }

        protected override bool DoVerify(DataRow dr, int i)
        {
            return true;
        }

        protected override bool DoFix(DataRow dr, int i)
        {
            return true;
        }
    }

    // 默认值约束
    // 默认在DataTable中的类型是string
    public class DefaultValueConstraint : ConstraintHint
    {
        public string Value { get; set; }
        public DefaultValueConstraint(string columnName, string defaultValue = "")
            : base(columnName)
        {
            Value = defaultValue;
        }

        protected override bool DoVerify(DataRow dr, int i)
        {
            return true;
        }

        protected override bool DoFix(DataRow dr, int i)
        {
            if (dr[ColumnNameNeedsConstraint] == "")
            {
                dr[ColumnNameNeedsConstraint] = Value;
            }

            return true;
        }
    }

    // 外键约束控制
    // 默认在DataTable中的类型是string
    // 构造函数中的列名参数表示的意思是本表的键和外表对应的主键，如果表中有冗余，那么可以通过
    // AssociatedColumns属性进行添加对应关系。
    public class ForeignKeyConstraint : ConstraintHint
    {
        #region Fields
        // ThisTableForeignKeyColumnName故意设置得与ColumnNameNeedsConstraint相同语义，作为别名出现而已
        public string ThisTableForeignKeyColumnName
        {
            get { return ColumnNameNeedsConstraint; }
            set { ColumnNameNeedsConstraint = value; }
        }

        // 与ColumnNameNeedsConstraint列对应的外键
        public string OtherTablePrimaryKeyColumnName { get; set; }

        // 在显示时依托在主键之后的表述性文字所在的列名
        public string OtherTableDisplayColumnName { get; set; }

        // 冗余对应列集合，即第一个参数所代表的列要与第二个参数所代表的列值相同
        // （不包括主键和外键的对应，只有冗余列的对应关系）
        private Dictionary<string, string> associatedColumns = new Dictionary<string, string>();
        public Dictionary<string, string> AssociatedColumns
        {
            get
            {
                return associatedColumns;
            }
        }
        #endregion

        public ForeignKeyConstraint(
            string thisForeignKeyColumnName,
            string otherForeignKeyColumnName = "",
            string otherDisplaceColumnName = "",
            DataTable foreignTable = null)
            : base(thisForeignKeyColumnName, foreignTable)
        {
            ThisTableForeignKeyColumnName = thisForeignKeyColumnName;
            OtherTablePrimaryKeyColumnName = otherForeignKeyColumnName;
            OtherTableDisplayColumnName = otherDisplaceColumnName;
        }

        protected override bool DoVerify(DataRow dr, int i)
        {
            if (ReferenceTable == null)
            {
                // TODO: log 
                return false;
            }

            bool result = true;
    
            // 测试键的对应关系,只需要包含即可
            foreach (KeyValuePair<string, string> pair in AssociatedColumns)
            {
                int j = 0;
                for (; j < ReferenceTable.Rows.Count; j++)
                {
                    string str1 = dr[pair.Key.ToString()].ToString();
                    string str2 = ReferenceTable.Rows[j][pair.Value.ToString()].ToString();
                    result =  str1 == str2;
                    if (result == true)
                    {
                        break;
                    }
                }

                if (j == ReferenceTable.Rows.Count && result == false)
                {
                    return false;
                }
            }

            return result;
        }

        protected override bool DoFix(DataRow dr, int i)
        {
            if (ReferenceTable == null)
            {
                // TODO: log 
                return false;
            }
            
            // 因为所有的外键都被设置为readonly，所以不需要做额外的检查工作了,就直接根据主键赋值即可

            // 先获取外键当前值在ReferenceTable中代表主键时所对应的行索引
            int j = 0;
            for (; j < ReferenceTable.Rows.Count; j++)
            {
                if (dr[ThisTableForeignKeyColumnName].ToString() == ReferenceTable.Rows[j][OtherTablePrimaryKeyColumnName].ToString())
                {
                    break;
                }
            }
            //如果是空行j == ReferenceTable.Rows.Count
            if (j != ReferenceTable.Rows.Count)
            {
                foreach (KeyValuePair<string, string> pair in AssociatedColumns)
                {
                    dr[pair.Key.ToString()] = ReferenceTable.Rows[j][pair.Value.ToString()];
                }
            }
            
            return true;
        }
    }
}
